
#ifndef Velyn_MaterialManager_h
#define Velyn_MaterialManager_h

#include <map>
#include <string>
#include <cstdio>

#include "Point3.h"

// TODO: put this in color.h
typedef Velyn::Object::Point3 Color;

// TODO: put this in material.h
// which also #include "color.h"
typedef struct {
    Color ambient, diffuse, specular;
    float shininess;
} Material;

namespace Velyn {

    /* 
     * MaterialManager uses a singleton design pattern.
     *
     * @author n7down
     */
    class MaterialManager {

    private:
        
        /* 
         * The MaterialManager default constuctor.
         */
        MaterialManager() {}

        /*
         * The instances that is used for the MaterialManager.
         */
        static MaterialManager* instance;
        
        /*
         * The map where the materials and their associated names are stored.
         */
        std::map<std::string, Material> materialMap;
        
    public:
        
        /*
         * MaterialManager deconstructor.
         */
        ~MaterialManager() {
            delete instance;
        }
        
        /*
         * Returns the singleton for MaterialManager.
         */
        static MaterialManager* getInstance() {
            if(instance == NULL) {
                instance = new MaterialManager();
            }
            
            return instance;
        }
        
        /*
         * Loads materials from a textfile into the map, if it was unable to find the file,
         * it throws an error.
         *
         * @param 
         * @throws
         */
        void loadMaterials(const char* filename) {
            FILE* file = std::fopen(filename, "r");
            
            char buffer[255];
            char n[20];
            
            Material material;
            
            // if the file exists
            if(file) {
                
                // TODO: if the line contains a format that is not %s %f %f %f %f %f %f %f %f %f %f\n
                // then throw an error or log a severe then skip the line.
                // look at the return value of sscanf.
                
                // get the information
                while(std::fgets(buffer, 255, file) != NULL) {
                    
                    if(std::sscanf(buffer, 
                                   "%s %f %f %f %f %f %f %f %f %f %f\n",
                                   &n,
                                   &material.ambient.r, &material.ambient.g, &material.ambient.b,
                                   &material.diffuse.r, &material.diffuse.g, &material.diffuse.b,
                                   &material.specular.r, &material.specular.g, &material.specular.b,
                                   &material.shininess) != 11) {
                        
                        // show logging error that format was not recognized
                    }
                    
                    else {
                       
                        // convert the char name to a string
                        std::string name(n);
                    
                        // put the material in the map
                        materialMap[name] = material;
                    }
                } 
            } 
            
            else {
                std::printf("File does not exist!\n");
                
                // throw an error
                // throw FileNotFound(filename);
            }
            
            std::fclose(file);
        }
        
        /*
         * Applies a material from the map from its name, if it does not exist
         * in the map then the default material is used.
         *
         * @param
         */
        void apply(const std::string name) {
            Material material;
            
            std::map<std::string, Material>::const_iterator materialIterator;
            
            // this assumes that the material exists in the materialMap
            materialIterator = materialMap.find(name);
            
            // if the specified material does not exist
            if(materialIterator == materialMap.end()) {
                
                // set default values
                material.ambient.set(0.5f, 0.5f, 0.5f);
                material.diffuse.set(0.5f, 0.5f, 0.5f);
                material.specular.set(0.5f, 0.5f, 0.5f);
                material.shininess = 50.0f;
                
            } 
            
            else {
            
                // otherwise load the the material that is the map.
                material = materialIterator->second;
            }
                
            glMaterialfv(GL_FRONT_AND_BACK, GL_AMBIENT, material.ambient.get());
            glMaterialfv(GL_FRONT_AND_BACK, GL_DIFFUSE, material.diffuse.get());
            glMaterialfv(GL_FRONT_AND_BACK, GL_SPECULAR, material.specular.get());
            glMaterialf(GL_FRONT_AND_BACK, GL_SHININESS, material.shininess);
        }
        
        void unapply() {}
    };

    MaterialManager* MaterialManager::instance = NULL;
}
        
#endif
